/* ibm1130_gui.c: IBM 1130 CPU simulator Console Display
 *
 *  Based on the SIMH package written by Robert M Supnik
 *
 * (C) Copyright 2002, Brian Knittel.
 * You may freely use this program, but: it offered strictly on an AS-IS, AT YOUR OWN
 * RISK basis, there is no warranty of fitness for any purpose, and the rest of the
 * usual yada-yada. Please keep this notice and the copyright in any distributions
 * or modifications.
 *
 * This is not a supported product, but I welcome bug reports and fixes.
 * Mail to simh@ibm1130.org
 *
 * 30-Dec-05 BLK    Fixed mask for IAR and SAR register display and added display
 *                  of Arithmetic Factor, per Carl Claunch.
 *
 * 09-Apr-04 BLK    Changed code to use stock windows cursor IDC_HAND if available
 *
 * 02-Dec-02 BLK    Changed display, added printer and card reader icons
 *                  Added drag and drop support for scripts and card decks
 *                  Added support for physical card reader and printer (hides icons)
 *
 * 17-May-02 BLK    Pulled out of ibm1130_cpu.c
 */

/* ------------------------------------------------------------------------
 * Definitions
 * ------------------------------------------------------------------------ */

#include "ibm1130_defs.h"
#include "ibm1130res.h"
#include "sim_tmxr.h"

#define UPDATE_BY_TIMER

#ifdef UPDATE_BY_TIMER
#  define UPDATE_INTERVAL   20              /* set to desired number of updates/second */
#else
#  define UPDATE_INTERVAL   5000            /* GUI: set to 100000/f where f = desired updates/second of 1130 time */
#endif

#define UNIT_V_CR_EMPTY    (UNIT_V_UF + 5)          /* NOTE: THESE MUST MATCH THE DEFINITION IN ibm1130_cr.c */
#define UNIT_CR_EMPTY      (1u << UNIT_V_CR_EMPTY)
#define UNIT_V_PHYSICAL    (UNIT_V_UF + 9)
#define UNIT_PHYSICAL      (1u << UNIT_V_PHYSICAL)

#define UNIT_V_PHYSICAL_PTR (UNIT_V_UF + 10)        /* NOTE: THESE MUST MATCH THE DEFINITION IN ibm1130_prt.c */
#define UNIT_PHYSICAL_PTR (1u << UNIT_V_PHYSICAL_PTR)

/* I think I had it wrong; Program Load actually does start the processor after
 * reading in the card?
 */

#define PROGRAM_LOAD_STARTS_CPU

/* ------------------------------------------------------------------------
 * Function declarations
 * ------------------------------------------------------------------------ */

t_stat console_reset (DEVICE *dptr);

/* ------------------------------------------------------------------------ 
 * Console display - on Windows builds (only) this code displays the 1130 console
 * and toggle switches. It really enhances the experience.
 *
 * Currently, when the IPS throttle is nonzero, I update the display after every
 * UPDATE_INTERVAL instructions, plus or minus a random amount to avoid aliased
 * sampling in loops.  When UPDATE_INTERVAL is defined as zero, we update every
 * instruction no matter what the throttle. This makes the simulator too slow
 * but it's cool and helpful during development.
 * ------------------------------------------------------------------------ */

#define UNIT_V_DISPLAY   (UNIT_V_UF + 0)
#define UNIT_DISPLAY     (1u << UNIT_V_DISPLAY)

MTAB console_mod[] = {
    { UNIT_DISPLAY, 0,            "off", "OFF", NULL },
    { UNIT_DISPLAY, UNIT_DISPLAY, "on",  "ON",  NULL },
    { 0 }
};

UNIT console_unit = {UDATA (NULL, UNIT_DISABLE|UNIT_DISPLAY, 0) };

DEVICE console_dev = {
    "GUI", &console_unit, NULL, console_mod,
    1, 16, 16, 1, 16, 16,
    NULL, NULL, console_reset,
    NULL, NULL, NULL
};

/* reset for the "console" display device  */

extern UNIT cr_unit;                                    /* pointers to 1442 and 1132 (1403) printers */
extern UNIT prt_unit;

extern UNIT dsk_unit[];
extern int boot_drive;
extern t_bool program_is_loaded;

#ifndef GUI_SUPPORT
    void update_gui (int force)               {}        /* stubs for non-GUI builds */
    void forms_check (int set)                {}
    void print_check (int set)                {}
    void keyboard_select (int select)         {}
    void keyboard_selected (int select)       {}
    void disk_ready (int ready)               {}
    void disk_unlocked (int unlocked)         {}
    void gui_run (int running)                {} 

    t_stat console_reset (DEVICE *dptr)                         {return SCPE_OK;}
    long   stuff_cmd (char *cmd)                                {return 0;}
    t_bool stuff_and_wait (char *cmd, int timeout, int delay)   {return FALSE;}
    char  *read_cmdline (char *ptr, int size, FILE *stream)     {return read_line(ptr, size, stream);}
    void   remark_cmd (char *remark)                            {sim_printf("%s\n", remark);}
#else

static HWND hConsoleWindow = NULL;

t_stat console_reset (DEVICE *dptr)
{
    if (! sim_gui) {
        SETBIT(console_unit.flags, UNIT_DIS);           /* disable the GUI */
        CLRBIT(console_unit.flags, UNIT_DISPLAY);       /* turn the GUI off */
    } else {
        if (!hConsoleWindow)
            hConsoleWindow = GetConsoleWindow();
        }

    return SCPE_OK;
}

/* scp_panic - report fatal internal programming error */

void scp_panic (const char *msg)
{
    fprintf(stderr, "%s\n", msg);
    exit(1);
}

#ifdef _WIN32
    /* only _WIN32 is defined right now */

#include <windows.h>

#define BUTTON_WIDTH  90
#define BUTTON_HEIGHT 50

#define IDC_UNUSED              0
#define IDC_DISK_UNLOCK         1
#define IDC_RUN                 2
#define IDC_KEYBOARD_SELECT     3
#define IDC_POWER_ON            4
#define IDC_FILE_READY          5
#define IDC_PARITY_CHECK        6
#define IDC_FORMS_CHECK         7
#define IDC_POWER               8
#define IDC_PROGRAM_START       9
#define IDC_PROGRAM_STOP        10
#define IDC_LOAD_IAR            11
#define IDC_KEYBOARD            12
#define IDC_IMM_STOP            13
#define IDC_RESET               14
#define IDC_PROGRAM_LOAD        15

#define IDC_TEAR                16  /* standard button */
#define IDC_1442                17  /* device images */
#define IDC_1132                18

#define LAMPTIME        500         /* 500 msec delay on updating */
#define FLASH_TIMER_ID    1
#define UPDATE_TIMER_ID   2

#define RUNSWITCH_X 689             /* center of the run mode switch dial */
#define RUNSWITCH_Y 107
#define TOGGLES_X   122             /* left edge of series of toggle switches */

#define TXTBOX_X    200             /* text labels showing attached devices */
#define TXTBOX_Y    300
#define TXTBOX_WIDTH    195
#define TXTBOX_HEIGHT    12

static BOOL   class_defined = FALSE;
static HWND   hConsoleWnd = NULL;
static HBITMAP hBitmap = NULL;
static HFONT  hFont = NULL;
static HFONT  hBtnFont = NULL;
static HFONT  hTinyFont = NULL;
static HBRUSH hbLampOut = NULL;
static HBRUSH hbWhite = NULL;
static HBRUSH hbBlack = NULL;
static HBRUSH hbGray  = NULL;
static HPEN   hSwitchPen = NULL;
static HPEN   hWhitePen  = NULL;
static HPEN   hBlackPen  = NULL;
static HPEN   hLtGreyPen = NULL;
static HPEN   hGreyPen   = NULL;
static HPEN   hDkGreyPen = NULL;
static int    hUpdateTimer = 0;
static int    hFlashTimer  = 0;

static HCURSOR hcArrow = NULL;
static HCURSOR hcHand  = NULL;
static HINSTANCE hInstance;
static HDC hCDC = NULL;
static char szConsoleClassName[] = "1130CONSOLE";
static DWORD PumpID = 0;
static HANDLE hPump = INVALID_HANDLE_VALUE;
static HANDLE hPumpReadyEvent = NULL;

static int bmwid, bmht;
static HANDLE hbm1442_full, hbm1442_empty, hbm1442_eof, hbm1442_middle;
static HANDLE hbm1132_full, hbm1132_empty;

static struct tag_btn {
    int x, y, wx, wy;
    char *txt;
    BOOL pushable, state;
    COLORREF clr;
    HBRUSH hbrLit, hbrDark;
    HWND   hBtn;
    BOOL   subclassed;

} btn[] = {
    0, 0, BUTTON_WIDTH, BUTTON_HEIGHT,  "",                     FALSE,  FALSE,  RGB(255,255,180),   NULL, NULL, NULL,   TRUE,
    0, 1, BUTTON_WIDTH, BUTTON_HEIGHT,  "DISK\nUNLOCK",         FALSE,  TRUE,   RGB(255,255,180),   NULL, NULL, NULL,   TRUE,
    0, 2, BUTTON_WIDTH, BUTTON_HEIGHT,  "RUN",                  FALSE,  FALSE,  RGB(0,255,0),       NULL, NULL, NULL,   TRUE,
    0, 3, BUTTON_WIDTH, BUTTON_HEIGHT,  "K B\nSELECT",          FALSE,  FALSE,  RGB(255,255,180),   NULL, NULL, NULL,   TRUE,

    1, 0, BUTTON_WIDTH, BUTTON_HEIGHT,  "POWER\nON",            FALSE,  TRUE,   RGB(255,255,180),   NULL, NULL, NULL,   TRUE,
    1, 1, BUTTON_WIDTH, BUTTON_HEIGHT,  "FILE\nREADY",          FALSE,  FALSE,  RGB(0,255,0),       NULL, NULL, NULL,   TRUE,
    1, 2, BUTTON_WIDTH, BUTTON_HEIGHT,  "PARITY\nCHECK",        FALSE,  FALSE,  RGB(255,0,0),       NULL, NULL, NULL,   TRUE,
    1, 3, BUTTON_WIDTH, BUTTON_HEIGHT,  "FORMS\nCHECK",         FALSE,  FALSE,  RGB(255,255,0),     NULL, NULL, NULL,   TRUE,

    2, 0, BUTTON_WIDTH, BUTTON_HEIGHT,  "POWER",                TRUE,   FALSE,  RGB(255,255,180),   NULL, NULL, NULL,   TRUE,
    2, 1, BUTTON_WIDTH, BUTTON_HEIGHT,  "PROGRAM\nSTART",       TRUE,   FALSE,  RGB(0,255,0),       NULL, NULL, NULL,   TRUE,
    2, 2, BUTTON_WIDTH, BUTTON_HEIGHT,  "PROGRAM\nSTOP",        TRUE,   FALSE,  RGB(255,0,0),       NULL, NULL, NULL,   TRUE,
    2, 3, BUTTON_WIDTH, BUTTON_HEIGHT,  "LOAD\nIAR",            TRUE,   FALSE,  RGB(0,0,255),       NULL, NULL, NULL,   TRUE,

    3, 0, BUTTON_WIDTH, BUTTON_HEIGHT,  "KEYBOARD",             TRUE,   FALSE,  RGB(255,255,180),   NULL, NULL, NULL,   TRUE,
    3, 1, BUTTON_WIDTH, BUTTON_HEIGHT,  "IMM\nSTOP",            TRUE,   FALSE,  RGB(255,0,0),       NULL, NULL, NULL,   TRUE,
    3, 2, BUTTON_WIDTH, BUTTON_HEIGHT,  "RESET",                TRUE,   FALSE,  RGB(0,0,255),       NULL, NULL, NULL,   TRUE,
    3, 3, BUTTON_WIDTH, BUTTON_HEIGHT,  "PROGRAM\nLOAD",        TRUE,   FALSE,  RGB(0,0,255),       NULL, NULL, NULL,   TRUE,

    TXTBOX_X+40, TXTBOX_Y+25, 35, 12,   "Tear",                 TRUE,   FALSE,  0,                  NULL, NULL, NULL,   FALSE,
    635, 238, 110, 110,                 "EMPTY_1442",           TRUE,   FALSE,  0,                  NULL, NULL, NULL,   FALSE,
    635, 366, 110, 110,                 "EMPTY_1132",           TRUE,   FALSE,  0,                  NULL, NULL, NULL,   FALSE,
};
#define NBUTTONS (sizeof(btn) / sizeof(btn[0]))

#define STATE_1442_EMPTY    0       /* no cards (no file attached) */
#define STATE_1442_FULL     1       /* cards in hopper (file attached at BOF) */
#define STATE_1442_MIDDLE   2       /* cards in hopper and stacker (file attached, neither EOF nor BOF) */
#define STATE_1442_EOF      3       /* cards in stacker (file attached, at EOF) */
#define STATE_1442_HIDDEN   4       /* simulator is attached to physical card reader */

#define STATE_1132_EMPTY    0       /* no paper hanging out of printer */
#define STATE_1132_FULL     1       /* paper hanging out of printer */
#define STATE_1132_HIDDEN   2       /* printer is attached to physical printer */

static struct tag_txtbox {
    int x, y;
    char *txt;
    char *unitname;
    int idctrl;
} txtbox[] = {
    TXTBOX_X, TXTBOX_Y,     "Card Reader",  "CR",       -1,
    TXTBOX_X, TXTBOX_Y+ 25, "Printer",      "PRT",      IDC_1132,
    TXTBOX_X, TXTBOX_Y+ 50, "Disk 0",       "DSK0",     -1,
    TXTBOX_X, TXTBOX_Y+ 75, "Disk 1",       "DSK1",     -1,
    TXTBOX_X, TXTBOX_Y+100, "Disk 2",       "DSK2",     -1,
    TXTBOX_X, TXTBOX_Y+125, "Disk 3",       "DSK3",     -1,
    TXTBOX_X, TXTBOX_Y+150, "Disk 4",       "DSK4",     -1,
};
#define NTXTBOXES (sizeof(txtbox) / sizeof(txtbox[0]))

#define TXTBOX_BOTTOM   (TXTBOX_Y+150)

static void init_console_window (void);
static void destroy_console_window (void);
LRESULT CALLBACK ConsoleWndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
static DWORD WINAPI Pump (LPVOID arg);
static void accept_dropped_file (HANDLE hDrop);
static void tear_printer (void);

#define NIXOBJECT(hObj) if (hObj != NULL) {DeleteObject(hObj); hObj = NULL;}

/* ------------------------------------------------------------------------ 
 * init_console_window - display the 1130 console. Actually just creates a thread 
 * to run the Pump routine which does the actual work.
 * ------------------------------------------------------------------------ */

static void init_console_window (void)
{
    static BOOL did_atexit = FALSE;

    if (hConsoleWnd != NULL)
        return;

    if ((hPumpReadyEvent  = CreateEvent(NULL, FALSE, FALSE, NULL)) == NULL)
        scp_panic("Can't create pump ready event");

    if (PumpID == 0)
        hPump = CreateThread(NULL, 0, Pump, 0, 0, &PumpID);

    WaitForSingleObject (hPumpReadyEvent, INFINITE);

    if (! did_atexit) {
        atexit(destroy_console_window);
        did_atexit = TRUE;
    }
}

/* ------------------------------------------------------------------------ 
 * destroy_console_window - delete GDI objects.
 * ------------------------------------------------------------------------ */

static void destroy_console_window (void)
{
    int i;

    if (hConsoleWnd != NULL)
        SendMessage(hConsoleWnd, WM_CLOSE, 0, 0);   /* cross thread call is OK */

    if (hPump != INVALID_HANDLE_VALUE) {            /* this is not the most graceful way to do it */
        TerminateThread(hPump, 0);
        hPump  = INVALID_HANDLE_VALUE;
        PumpID = 0;
        hConsoleWnd = NULL;
    }

    if (hPumpReadyEvent != NULL) {
        CloseHandle (hPumpReadyEvent);
        hPumpReadyEvent = NULL;
    }

    if (hCDC != NULL) {
        DeleteDC(hCDC);
        hCDC = NULL;
    }

    NIXOBJECT(hBitmap)
    NIXOBJECT(hbLampOut)
    NIXOBJECT(hFont)
    NIXOBJECT(hBtnFont);
    NIXOBJECT(hTinyFont);
    NIXOBJECT(hcHand)
    NIXOBJECT(hSwitchPen)
    NIXOBJECT(hLtGreyPen)
    NIXOBJECT(hGreyPen)
    NIXOBJECT(hDkGreyPen)

    for (i = 0; i < NBUTTONS; i++) {
        NIXOBJECT(btn[i].hbrLit);
        NIXOBJECT(btn[i].hbrDark);
    }

/*  if (class_defined) {
        UnregisterClass(hInstance, szConsoleClassName);
        class_defined = FALSE;
    }
*/
}

/* ------------------------------------------------------------------------ 
 * these variables hold the displayed versions of the system registers 
 * ------------------------------------------------------------------------ */

static int shown_iar = 0, shown_sar = 0, shown_sbr = 0, shown_afr = 0, shown_acc = 0, shown_ext  = 0;
static int shown_op  = 0, shown_tag = 0, shown_irq = 0, shown_ccc = 0, shown_cnd = 0, shown_wait = 0;
static int shown_ces = 0, shown_arf = 0, shown_runmode = MODE_RUN;
static int CND;

/* ------------------------------------------------------------------------ 
 * RedrawRegion - mark a region for redrawing without background erase
 * ------------------------------------------------------------------------ */

static void RedrawRegion (HWND hWnd, int left, int top, int right, int bottom)
{
    RECT r;

    r.left   = left;
    r.top    = top;
    r.right  = right;
    r.bottom = bottom;

    InvalidateRect(hWnd, &r, FALSE);
}

/* ------------------------------------------------------------------------ 
 * RepaintRegion - mark a region for redrawing with background erase
 * ------------------------------------------------------------------------ */

static void RepaintRegion (HWND hWnd, int left, int top, int right, int bottom)
{
    RECT r;

    r.left   = left;
    r.top    = top;
    r.right  = right;
    r.bottom = bottom;

    InvalidateRect(hWnd, &r, TRUE);
}

/* ------------------------------------------------------------------------ 
 * update_gui - sees if anything on the console display has changed, and invalidates 
 * the changed regions. Then it calls UpdateWindow to force an immediate repaint. This
 * function (update_gui) should probably not be called every time through the main
 * instruction loop but it should be called at least whenever wait_state or int_req change, and then
 * every so many instructions.  It's also called after every simh command so manual changes are
 * reflected instantly.
 * ------------------------------------------------------------------------ */

void update_gui (BOOL force)
{   
    int i;
    BOOL state;
    static int in_here = FALSE;
    static int32 displayed = 0;
    RECT xin;

    if ((int32)(console_unit.flags & UNIT_DISPLAY) != displayed) {      /* setting has changed */
        displayed = console_unit.flags & UNIT_DISPLAY;
        if (displayed)
            init_console_window();
        else
            destroy_console_window();
    }

    if (hConsoleWnd == NULL)
        return;

    GUI_BEGIN_CRITICAL_SECTION              /* only one thread at a time, please */
    if (in_here) {
        GUI_END_CRITICAL_SECTION
        return;
    }
    in_here = TRUE;
    GUI_END_CRITICAL_SECTION

    CND = 0;    /* combine carry and V as two bits */
    if (C)
        CND |= 2;
    if (V)
        CND |= 1;

    if ((boot_drive<0) || (!program_is_loaded)) {
        boot_drive = CES & 7;
        if (boot_drive > 4)
            boot_drive = -1;
    }
    if ((boot_drive>=0) && (dsk_unit[boot_drive].flags&UNIT_ATT)) {
        disk_ready(TRUE);
        disk_unlocked(FALSE);
    }
    else {
        disk_ready(FALSE);
        disk_unlocked(TRUE);
    }

    int_lamps |= int_req;
    if (ipl >= 0)
        int_lamps |= (0x20 >> ipl);

    if (RUNMODE == MODE_LOAD)
        SBR = CES;          /* in load mode, SBR follows the console switches */

    if (IAR != shown_iar)
            {shown_iar = IAR;        RedrawRegion(hConsoleWnd, 75,    8, 364,  32);}    /* lamps: don't bother erasing bkgnd */
    if (SAR != shown_sar)
            {shown_sar = SAR;        RedrawRegion(hConsoleWnd, 75,   42, 364,  65);}
    if (ARF != shown_arf)
            {shown_arf = ARF;        RedrawRegion(hConsoleWnd, 75,  114, 364, 136);}
    if (ACC != shown_acc)
            {shown_acc = ACC;        RedrawRegion(hConsoleWnd, 75,  141, 364, 164);}
    if (EXT != shown_ext)
            {shown_ext = EXT;        RedrawRegion(hConsoleWnd, 75,  174, 364, 197);}
    if (SBR != shown_sbr)
            {shown_sbr = SBR;        RedrawRegion(hConsoleWnd, 75,   77, 364,  97);}
    if (OP  != shown_op)                     
            {shown_op  = OP;         RedrawRegion(hConsoleWnd, 501,   8, 595,  32);}
    if (TAG != shown_tag)
            {shown_tag = TAG;        RedrawRegion(hConsoleWnd, 501,  77, 595,  97);}

    if (int_lamps != shown_irq)
            {shown_irq = int_lamps;  RedrawRegion(hConsoleWnd, 501, 108, 595, 130);}

    if (CCC != shown_ccc)
            {shown_ccc = CCC;        RedrawRegion(hConsoleWnd, 501, 141, 595, 164);}
    if (CND != shown_cnd)
            {shown_cnd = CND;        RedrawRegion(hConsoleWnd, 501, 174, 595, 197);}
    if ((wait_state|wait_lamp) != shown_wait)
            {shown_wait= (wait_state|wait_lamp); RedrawRegion(hConsoleWnd, 380,  77, 414,  97);}
    if (CES != shown_ces)
            {shown_ces = CES;        RepaintRegion(hConsoleWnd, TOGGLES_X-7, 230, TOGGLES_X+360, 275);} /* console entry sw: do erase bkgnd */
    if (RUNMODE != shown_runmode)
            {shown_runmode = RUNMODE;RepaintRegion(hConsoleWnd, RUNSWITCH_X-50, RUNSWITCH_Y-50, RUNSWITCH_X+50, RUNSWITCH_Y+50);}

    int_lamps = 0;

    /* this loop works with lamp buttons that are calculated on-the-fly only */
    for (i = 0; i < NBUTTONS; i++) {
        if (btn[i].pushable)
            continue;

        switch (i) {
            case IDC_RUN:
                state = hFlashTimer || (running && ! wait_state);
                break;

/* this button is always off
            case IDC_PARITY_CHECK
*/

/* these buttons are enabled/disabled directly
            case IDC_POWER_ON:
            case IDC_FILE_READY:
            case IDC_FORMS_CHECK:
            case IDC_KEYBOARD_SELECT:
            case IDC_DISK_UNLOCK:
*/
            default:
                continue;
        }

        if (state != btn[i].state) {                /* state has changed */
            EnableWindow(btn[i].hBtn, state);
            btn[i].state = state;
        }
    }

    if (force) {                                    /* if force flag is set, update text region */
        SetRect(&xin, TXTBOX_X, TXTBOX_Y, TXTBOX_X+TXTBOX_WIDTH, TXTBOX_BOTTOM+2*TXTBOX_HEIGHT);
        InvalidateRect(hConsoleWnd, &xin, TRUE);
    }

    state = ((cr_unit.flags & UNIT_ATT) == 0) ? STATE_1442_EMPTY  :
             (cr_unit.flags & UNIT_PHYSICAL)  ? STATE_1442_HIDDEN :
             (cr_unit.flags & UNIT_CR_EMPTY)  ? STATE_1442_EOF    : 
              cr_unit.pos                     ? STATE_1442_MIDDLE :
                                                STATE_1442_FULL;

    if (state != btn[IDC_1442].state) {
        if (state == STATE_1442_HIDDEN)
            ShowWindow(btn[IDC_1442].hBtn, SW_HIDE);
        else {
            if (btn[IDC_1442].state == STATE_1442_HIDDEN)
                ShowWindow(btn[IDC_1442].hBtn, SW_SHOWNA);

            SendMessage(btn[IDC_1442].hBtn, STM_SETIMAGE, IMAGE_BITMAP,
                (LPARAM) (
                    (state == STATE_1442_FULL)   ? hbm1442_full   :
                    (state == STATE_1442_MIDDLE) ? hbm1442_middle : 
                    (state == STATE_1442_EOF)    ? hbm1442_eof    :
                    hbm1442_empty));
        }

        btn[IDC_1442].state = state;
    }

    state = ((prt_unit.flags & UNIT_ATT) == 0)    ? STATE_1132_EMPTY  :
             (prt_unit.flags & UNIT_PHYSICAL_PTR) ? STATE_1132_HIDDEN :
              prt_unit.pos                        ? STATE_1132_FULL :
                                                    STATE_1132_EMPTY;

    if (state != btn[IDC_1132].state) {
        if (state == STATE_1132_HIDDEN)
            ShowWindow(btn[IDC_1132].hBtn, SW_HIDE);
        else {
            if (btn[IDC_1132].state == STATE_1132_HIDDEN)
                ShowWindow(btn[IDC_1132].hBtn, SW_SHOWNA);

            SendMessage(btn[IDC_1132].hBtn, STM_SETIMAGE, IMAGE_BITMAP,
                (LPARAM) (
                    (state == STATE_1132_FULL) ? hbm1132_full : hbm1132_empty));
        }

        btn[IDC_1132].state = state;
    }

    in_here = FALSE;
}

WNDPROC oldButtonProc = NULL;

/* ------------------------------------------------------------------------ 
 * ------------------------------------------------------------------------ */

LRESULT CALLBACK ButtonProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    int i;

    i = GetWindowLongPtr(hWnd, GWLP_ID);

    if (! btn[i].pushable) {
        if (uMsg == WM_LBUTTONDOWN || uMsg == WM_LBUTTONUP || uMsg == WM_LBUTTONDBLCLK)
            return 0;

        if (uMsg == WM_CHAR)
            if ((TCHAR) wParam == ' ')
                return 0;
    }

    return CallWindowProc(oldButtonProc, hWnd, uMsg, wParam, lParam);
}

/* ------------------------------------------------------------------------ 
 * ------------------------------------------------------------------------ */

static int occurs (char *txt, char ch)
{
    int count = 0;

    while (*txt)
        if (*txt++ == ch)
            count++;

    return count;
}

/* ------------------------------------------------------------------------ 
 * turns out to get properly colored buttons you have to paint them yourself. Sheesh.
 * On the plus side, this lets do a better job of aligning the button text than
 * the button would by itself.
 * ------------------------------------------------------------------------ */

void PaintButton (LPDRAWITEMSTRUCT dis)
{
    int i = dis->CtlID, nc, nlines, x, y, dy;
    BOOL down = dis->itemState & ODS_SELECTED;
    HPEN hOldPen;
    HFONT hOldFont;
    UINT oldAlign;
    COLORREF oldBk;
    char *txt, *tstart;

    if (! BETWEEN(i, 0, NBUTTONS-1))
        return;

    if (! btn[i].subclassed)
        return;

    FillRect(dis->hDC, &dis->rcItem, ((btn[i].pushable || power) && IsWindowEnabled(btn[i].hBtn)) ? btn[i].hbrLit : btn[i].hbrDark);

    if (! btn[i].pushable) {
        hOldPen = SelectObject(dis->hDC, hBlackPen);
        MoveToEx(dis->hDC, dis->rcItem.left,    dis->rcItem.top, NULL);
        LineTo(dis->hDC,   dis->rcItem.right-1, dis->rcItem.top);
        LineTo(dis->hDC,   dis->rcItem.right-1, dis->rcItem.bottom-1);
        LineTo(dis->hDC,   dis->rcItem.left,    dis->rcItem.bottom-1);
        LineTo(dis->hDC,   dis->rcItem.left,    dis->rcItem.top);
    }
    else if (down) {
        /* do the three-D thing */
        hOldPen = SelectObject(dis->hDC, hDkGreyPen);
        MoveToEx(dis->hDC, dis->rcItem.left,    dis->rcItem.bottom-2, NULL);
        LineTo(dis->hDC,   dis->rcItem.left,    dis->rcItem.top);
        LineTo(dis->hDC,   dis->rcItem.right-1, dis->rcItem.top);

        SelectObject(dis->hDC, hWhitePen);
        MoveToEx(dis->hDC, dis->rcItem.left,    dis->rcItem.bottom-1, NULL);
        LineTo(dis->hDC,   dis->rcItem.right-1, dis->rcItem.bottom-1);
        LineTo(dis->hDC,   dis->rcItem.right-1, dis->rcItem.top);

        SelectObject(dis->hDC, hGreyPen);
        MoveToEx(dis->hDC, dis->rcItem.left+1,  dis->rcItem.bottom-3, NULL);
        LineTo(dis->hDC,   dis->rcItem.left+1,  dis->rcItem.top+1);
        LineTo(dis->hDC,   dis->rcItem.right-3, dis->rcItem.top+1);
    }
    else {
        hOldPen = SelectObject(dis->hDC, hWhitePen);
        MoveToEx(dis->hDC, dis->rcItem.left,    dis->rcItem.bottom-2, NULL);
        LineTo(dis->hDC,   dis->rcItem.left,    dis->rcItem.top);
        LineTo(dis->hDC,   dis->rcItem.right-1, dis->rcItem.top);

        SelectObject(dis->hDC, hDkGreyPen);
        MoveToEx(dis->hDC, dis->rcItem.left,    dis->rcItem.bottom-1, NULL);
        LineTo(dis->hDC,   dis->rcItem.right-1, dis->rcItem.bottom-1);
        LineTo(dis->hDC,   dis->rcItem.right-1, dis->rcItem.top);

        SelectObject(dis->hDC, hGreyPen);
        MoveToEx(dis->hDC, dis->rcItem.left+1,  dis->rcItem.bottom-2, NULL);
        LineTo(dis->hDC,   dis->rcItem.right-2, dis->rcItem.bottom-2);
        LineTo(dis->hDC,   dis->rcItem.right-2, dis->rcItem.top+1);
    }

    SelectObject(dis->hDC, hOldPen);

    hOldFont = SelectObject(dis->hDC, hBtnFont);
    oldAlign = SetTextAlign(dis->hDC, TA_CENTER|TA_TOP);
    oldBk    = SetBkMode(dis->hDC, TRANSPARENT);

    txt = btn[i].txt;
    nlines = occurs(txt, '\n')+1;
    x  = (dis->rcItem.left + dis->rcItem.right)  / 2;
    y  = (dis->rcItem.top  + dis->rcItem.bottom) / 2;

    dy = 14;
    y  = y - (nlines*dy)/2;

    if (down) {
        x += 1;
        y += 1;
    }

    for (;;) {
        for (nc = 0, tstart = txt; *txt && *txt != '\n'; txt++, nc++)
            ;

        TextOut(dis->hDC, x, y, tstart, nc);

        if (*txt == '\0')
            break;

        txt++;
        y += dy;
    }

    SetTextAlign(dis->hDC, oldAlign);
    SetBkMode(dis->hDC,    oldBk);
    SelectObject(dis->hDC, hOldFont);
}
    
/* ------------------------------------------------------------------------ 
 * ------------------------------------------------------------------------ */

HWND CreateSubclassedButton (HWND hwParent, UINT_PTR i)
{
    HWND hBtn;
    int x, y;
    int r, g, b;

    y = bmht - (4*BUTTON_HEIGHT) + BUTTON_HEIGHT * btn[i].y;
    x = (btn[i].x < 2) ? (btn[i].x*BUTTON_WIDTH) : (598 - (4-btn[i].x)*BUTTON_WIDTH);

    if ((hBtn = CreateWindow("BUTTON", btn[i].txt, WS_CHILD|WS_VISIBLE|BS_CENTER|BS_MULTILINE|BS_OWNERDRAW,
            x, y, BUTTON_WIDTH, BUTTON_HEIGHT, hwParent, (HMENU) i, hInstance, NULL)) == NULL)
        return NULL;

    btn[i].hBtn = hBtn;

    if (oldButtonProc == NULL)
        oldButtonProc = (WNDPROC) GetWindowLongPtr(hBtn, GWLP_WNDPROC);

    btn[i].hbrLit = CreateSolidBrush(btn[i].clr);

    if (! btn[i].pushable) {
        r = GetRValue(btn[i].clr) / 4;
        g = GetGValue(btn[i].clr) / 4;
        b = GetBValue(btn[i].clr) / 4;

        btn[i].hbrDark = CreateSolidBrush(RGB(r,g,b));
        EnableWindow(hBtn, FALSE);
    }

    SetWindowLongPtr(hBtn, GWLP_WNDPROC, (UINT_PTR) ButtonProc);
    return hBtn;
}

/* ------------------------------------------------------------------------ 
 * Pump - thread that takes care of the console window. It has to be a separate thread so that it gets
 * execution time even when the simulator is compute-bound or IO-blocked. This routine creates the window
 * and runs a standard Windows message pump. The window function does the actual display work.
 * ------------------------------------------------------------------------ */

static DWORD WINAPI Pump (LPVOID arg)
{
    MSG msg;
    int wx, wy;
    UINT_PTR i;
    RECT r, ra;
    BITMAP bm;
    WNDCLASS cd;
    HDC hDC;
    HWND hActWnd;

    hActWnd = GetForegroundWindow();

    if (! class_defined) {                          /* register Window class */
        hInstance = GetModuleHandle(NULL);

        memset(&cd, 0, sizeof(cd));
        cd.style         = CS_NOCLOSE;
        cd.lpfnWndProc   = ConsoleWndProc;
        cd.cbClsExtra    = 0;
        cd.cbWndExtra    = 0;
        cd.hInstance     = hInstance;
        cd.hIcon         = NULL;
        cd.hCursor       = hcArrow;
        cd.hbrBackground = NULL;
        cd.lpszMenuName  = NULL;
        cd.lpszClassName = szConsoleClassName;

        if (! RegisterClass(&cd)) {
            PumpID = 0;
            return 0;
        }

        class_defined = TRUE;
    }

    hbWhite    = GetStockObject(WHITE_BRUSH);           /* create or fetch useful GDI objects */
    hbBlack    = GetStockObject(BLACK_BRUSH);           /* create or fetch useful GDI objects */
    hbGray     = GetStockObject(GRAY_BRUSH);
    hSwitchPen = CreatePen(PS_SOLID, 5, RGB(255,255,255));

    hWhitePen  = GetStockObject(WHITE_PEN);
    hBlackPen  = GetStockObject(BLACK_PEN);
    hLtGreyPen = CreatePen(PS_SOLID, 1, RGB(190,190,190));
    hGreyPen   = CreatePen(PS_SOLID, 1, RGB(128,128,128));
    hDkGreyPen = CreatePen(PS_SOLID, 1, RGB(64,64,64));

    hcArrow    = LoadCursor(NULL,      IDC_ARROW);
#ifdef IDC_HAND
    hcHand     = LoadCursor(NULL, IDC_HAND);                            /* use stock object provided by Windows */
    if (hcHand == NULL)
        hcHand = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_MYHAND));
#else
    hcHand     = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_MYHAND));
#endif

    if (hBitmap   == NULL)
        hBitmap   = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_CONSOLE));
    if (hbLampOut == NULL)
        hbLampOut = CreateSolidBrush(RGB(50,50,50));
    if (hFont     == NULL)
        hFont     = CreateFont(-10, 0, 0, 0, FW_NORMAL, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, FIXED_PITCH, FF_SWISS, "Arial");
    if (hBtnFont  == NULL)
        hBtnFont  = CreateFont(-12, 0, 0, 0, FW_NORMAL, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, FIXED_PITCH, FF_SWISS, "Arial");
    if (hTinyFont == NULL)
        hTinyFont = CreateFont(-10, 0, 0, 0, FW_NORMAL, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, FIXED_PITCH, FF_SWISS, "Arial");

    if (hConsoleWnd == NULL) {                      /* create window */
        if ((hConsoleWnd = CreateWindow(szConsoleClassName, "IBM 1130", WS_OVERLAPPED|WS_CLIPCHILDREN, 0, 0, 200, 200, NULL, NULL, hInstance, NULL)) == NULL) {
            PumpID = 0;
            return 0;
        }

        DragAcceptFiles(hConsoleWnd, TRUE);         /* let it accept dragged files (scripts) */
    }

    GetObject(hBitmap, sizeof(bm), &bm);            /* get bitmap size */
    bmwid = bm.bmWidth;
    bmht  = bm.bmHeight;

    for (i = 0; i < NBUTTONS; i++) {
        if (! btn[i].subclassed)
            continue;

        CreateSubclassedButton(hConsoleWnd, i);
        if (! btn[i].pushable)
            EnableWindow(btn[i].hBtn, btn[i].state);
    }

/* This isn't needed anymore, now that we have the big printer icon -- it acts like a button now
 *  i = IDC_TEAR;
 *  btn[i].hBtn = CreateWindow("BUTTON", btn[i].txt, WS_CHILD|WS_VISIBLE|BS_CENTER,
 *          btn[i].x, btn[i].y, btn[i].wx, btn[i].wy, hConsoleWnd, (HMENU) i, hInstance, NULL);
 *
 *  SendMessage(btn[i].hBtn, WM_SETFONT, (WPARAM) hTinyFont, TRUE);
 */

    hbm1442_full   = LoadBitmap(hInstance, "FULL_1442");
    hbm1442_empty  = LoadBitmap(hInstance, "EMPTY_1442");
    hbm1442_eof    = LoadBitmap(hInstance, "EOF_1442");
    hbm1442_middle = LoadBitmap(hInstance, "MIDDLE_1442");
    hbm1132_full   = LoadBitmap(hInstance, "FULL_1132");
    hbm1132_empty  = LoadBitmap(hInstance, "EMPTY_1132");

    i = IDC_1442;

    btn[i].hBtn = CreateWindow("STATIC", btn[i].txt, WS_CHILD|WS_VISIBLE|SS_BITMAP|SS_SUNKEN|WS_BORDER|SS_REALSIZEIMAGE|SS_NOTIFY,
            btn[i].x, btn[i].y, btn[i].wx, btn[i].wy, hConsoleWnd, (HMENU) i, hInstance, NULL);
    btn[i].state = STATE_1442_EMPTY;

    wx = SendMessage(btn[i].hBtn, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM) hbm1442_empty);

    i = IDC_1132;

    btn[i].hBtn = CreateWindow("STATIC", btn[i].txt, WS_CHILD|WS_VISIBLE|SS_BITMAP|SS_SUNKEN|WS_BORDER|SS_REALSIZEIMAGE|SS_NOTIFY,
            btn[i].x, btn[i].y, btn[i].wx, btn[i].wy, hConsoleWnd, (HMENU) i, hInstance, NULL);
    btn[i].state = FALSE;

    wx = SendMessage(btn[i].hBtn, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM) hbm1132_empty);

    GetWindowRect(hConsoleWnd, &r);                 /* get window size as created */
    wx = r.right  - r.left + 1;
    wy = r.bottom - r.top  + 1;

    if (hCDC == NULL) {                             /* get a memory DC and select the bitmap into ti */
        hDC = GetDC(hConsoleWnd);
        hCDC = CreateCompatibleDC(hDC);
        SelectObject(hCDC, hBitmap);
        ReleaseDC(hConsoleWnd, hDC);
    }

    GetClientRect(hConsoleWnd, &r);
    wx = (wx - r.right  - 1) + bmwid;               /* compute new desired size based on how client area came out */
    wy = (wy - r.bottom - 1) + bmht;
    MoveWindow(hConsoleWnd, 0, 0, wx, wy, FALSE);   /* resize window */

    ShowWindow(hConsoleWnd, SW_SHOWNOACTIVATE);     /* display it */
    UpdateWindow(hConsoleWnd);

    if (hActWnd != NULL) {                          /* bring console (sim) window back to top */
        GetWindowRect(hConsoleWnd, &r);
        ShowWindow(hActWnd, SW_NORMAL);             /* and move it just below the display window */
        SetWindowPos(hActWnd, HWND_TOP, 0, r.bottom, 0, 0, SWP_NOSIZE);
        GetWindowRect(hActWnd, &ra);
        if (ra.bottom >= GetSystemMetrics(SM_CYSCREEN)) {   /* resize if it goes of bottom of screen */
            ra.bottom = GetSystemMetrics(SM_CYSCREEN) - 1;
            SetWindowPos(hActWnd, 0, 0, 0, ra.right-ra.left+1, ra.bottom-ra.top+1, SWP_NOZORDER|SWP_NOMOVE);
        }
    }

    SetEvent (hPumpReadyEvent);

    if (running)                                    /* if simulator is already running, start update timer */
        gui_run(TRUE);

    while (GetMessage(&msg, hConsoleWnd, 0, 0)) {   /* message pump - this basically loops forevermore */
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    if (hConsoleWnd != NULL) { 
        DragAcceptFiles(hConsoleWnd, FALSE);        /* unregister as drag/drop target */
        DestroyWindow(hConsoleWnd);                 /* but if a quit message got posted, clean up */
        hConsoleWnd = NULL;
    }

    PumpID = 0;
    return 0;
}

/* ------------------------------------------------------------------------ 
 * DrawBits - starting at position (x,y), draw lamps for nbits bits of word 'bits', looking only at masked bits
 * ------------------------------------------------------------------------ */

static void DrawBits (HDC hDC, int x, int y, int bits, int nbits, int mask, char *syms)
{
    int i, b = 0x0001 << (nbits-1);

    for (i = 0; i < nbits; i++, b >>= 1) {
        if (mask & b) {                             /* select white or black lettering then write 2 chars */
            SetTextColor(hDC, (b & bits && power) ? RGB(255,255,255) : RGB(0,0,0));
            TextOut(hDC, x, y, syms, 2);
        }
        syms += 2;                                  /* go to next symbol pair */

        if (i < 10)
            x += 15;                                /* step between lamps */
        else
            x += 19;

        if (x < 500) {
            if (b & 0x1110)
                x += 10;                            /* step over nibble divisions on left side */
            else if (b & 0x0001)
                x += 9;
        }
    }
}

/* ------------------------------------------------------------------------ 
 * DrawToggles - display the console sense switches
 * ------------------------------------------------------------------------ */


static void DrawToggles (HDC hDC, int bits)
{
    int b, x;

    for (b = 0x8000, x = TOGGLES_X; b != 0; b >>= 1) {
        if (shown_ces & b) {            /* up */
            SelectObject(hDC, hbWhite);
            Rectangle(hDC, x, 232, x+9, 240);
            SelectObject(hDC, hbGray);
            Rectangle(hDC, x, 239, x+9, 255);
        }
        else {                          /* down */
            SelectObject(hDC, hbWhite);
            Rectangle(hDC, x, 263, x+9, 271);
            SelectObject(hDC, hbGray);
            Rectangle(hDC, x, 248, x+9, 264);
        }

        x += (b & 0x1111) ? 31 : 21;
    }
}

/* ------------------------------------------------------------------------ 
 * DrawRunmode - draw the run mode rotary switch's little tip
 * ------------------------------------------------------------------------ */

void DrawRunmode (HDC hDC, int mode)
{
    double angle = (mode*45. + 90.) * 3.1415926 / 180.;     /* convert mode position to angle in radians */
    double ca, sa;                                          /* sine and cosine */
    int x0, y0, x1, y1;
    HPEN hOldPen;

    ca = cos(angle);
    sa = sin(angle);

    x0 = RUNSWITCH_X + (int) (20.*ca + 0.5);        /* inner radius */
    y0 = RUNSWITCH_Y - (int) (20.*sa + 0.5);
    x1 = RUNSWITCH_X + (int) (25.*ca + 0.5);        /* outer radius */
    y1 = RUNSWITCH_Y - (int) (25.*sa + 0.5);

    hOldPen = SelectObject(hDC, hSwitchPen);

    MoveToEx(hDC, x0, y0, NULL);
    LineTo(hDC, x1, y1);

    SelectObject(hDC, hOldPen);
}

/* ------------------------------------------------------------------------ 
 * HandleClick - handle mouse clicks on the console window. Now we just 
 * look at the console sense switches.  Actual says this is a real click, rather
 * than a mouse-region test.  Return value TRUE means the cursor is over a hotspot.
 * ------------------------------------------------------------------------ */

static BOOL HandleClick (HWND hWnd, int xh, int yh, BOOL actual, BOOL rightclick)
{
    int b, x, r, ang, i;

    for (b = 0x8000, x = TOGGLES_X; b != 0; b >>= 1) {
        if (BETWEEN(xh, x-3, x+8+3) && BETWEEN(yh, 230, 275)) {
            if (actual) {
                CES ^= b;                       /* a hit. Invert the bit and redisplay */
                update_gui(TRUE);
            }
            return TRUE;
        }
        x += (b & 0x1111) ? 31 : 21;
    }

    if (BETWEEN(xh, RUNSWITCH_X-50, RUNSWITCH_X+50) && BETWEEN(yh, RUNSWITCH_Y-50, RUNSWITCH_Y+50)) {       /* hit near rotary switch */
        ang = (int) (atan2(RUNSWITCH_X-xh, RUNSWITCH_Y-yh)*180./3.1415926); /* this does implicit 90 deg rotation by the way */
        r = (int) sqrt((xh-RUNSWITCH_X)*(xh-RUNSWITCH_X)+(yh-RUNSWITCH_Y)*(yh-RUNSWITCH_Y));
        if (r > 12) {
            for (i = MODE_LOAD; i <= MODE_INT_RUN; i++) {
                if (BETWEEN(ang, i*45-12, i*45+12)) {
                    if (actual) {
                        RUNMODE = i;
                        update_gui(TRUE);
                    }
                    return TRUE;
                }
            }
            
        }
    }

    return FALSE;
}

/* ------------------------------------------------------------------------ 
 * DrawConsole - refresh the console display. (This routine could be sped up by intersecting
 * the various components' bounding rectangles with the repaint rectangle.  The bounding rects
 * could be put into an array and used both here and in the refresh routine).
 *
 * RedrawRegion -> force repaint w/o background redraw. used for lamps which are drawn in the same place in either state
 * RepaintRegion-> repaint with background redraw. Used for toggles which change position.
 * ------------------------------------------------------------------------ */

static void DrawConsole (HDC hDC, PAINTSTRUCT *ps)
{
    static char digits[] = " 0 1 2 3 4 5 6 7 8 9101112131415";
    static char cccs[]   = "3216 8 4 2 1";
    static char cnds[]   = " C V";
    static char waits[]  = " W";
    HFONT hOldFont, hOldBrush;
    RECT xout, xin;
    int i, n;
    DEVICE *dptr;
    UNIT *uptr;
    t_bool enab;
    char nametemp[50], *dispname;

    hOldFont  = SelectObject(hDC, hFont);           /* use that tiny font */
    hOldBrush = SelectObject(hDC, hbWhite);

    SetBkMode(hDC, TRANSPARENT);                    /* overlay letters w/o changing background */

    DrawBits(hDC,  76,  15, shown_iar,    16, mem_mask, digits);    /* register holds only 15 bits */
    DrawBits(hDC,  76,  48, shown_sar,    16, mem_mask, digits);    /* but let's display only used bits */
    DrawBits(hDC,  76,  81, shown_sbr,    16, 0xFFFF, digits);
    DrawBits(hDC,  76, 114, shown_arf,    16, 0xFFFF, digits);
    DrawBits(hDC,  76, 147, shown_acc,    16, 0xFFFF, digits);
    DrawBits(hDC,  76, 180, shown_ext,    16, 0xFFFF, digits);

    DrawBits(hDC, 506,  15, shown_op,      5, 0x001F, digits);
    DrawBits(hDC, 506,  81, shown_tag,     4, 0x0007, digits);
    DrawBits(hDC, 506, 114, shown_irq,     6, 0x003F, digits);
    DrawBits(hDC, 506, 147, shown_ccc,     6, 0x003F, cccs);
    DrawBits(hDC, 506, 180, shown_cnd,     2, 0x0003, cnds);

    DrawBits(hDC, 390,  81, shown_wait?1:0,1, 0x0001, waits);

    DrawToggles(hDC, shown_ces);

    DrawRunmode(hDC, shown_runmode);

    SelectObject(hDC, hOldFont);
    SelectObject(hDC, hOldBrush);

    SetBkColor(hDC, RGB(0,0,0));

    SetRect(&xin, TXTBOX_X, TXTBOX_Y, TXTBOX_X+TXTBOX_WIDTH, TXTBOX_BOTTOM+TXTBOX_HEIGHT);
    if (IntersectRect(&xout, &xin, &ps->rcPaint)) {
        hOldFont = SelectObject(hDC, hTinyFont);

        for (i = 0; i < NTXTBOXES; i++) {
            enab = FALSE;

            dptr = find_unit(txtbox[i].unitname, &uptr);
            if (dptr != NULL && uptr != NULL) {
                if (uptr->flags & UNIT_DIS) {
                    SetTextColor(hDC, RGB(128,0,0));
                }
                else if (uptr->flags & UNIT_ATT) {
                    SetTextColor(hDC, RGB(0,0,255));
                    if ((n = strlen(uptr->filename)) > 30) {
                        strcpy(nametemp, "...");
                        strcpy(nametemp+3, uptr->filename+n-30);
                        dispname = nametemp;                        
                    }
                    else
                        dispname = uptr->filename;

                    TextOut(hDC, txtbox[i].x+25, txtbox[i].y+TXTBOX_HEIGHT, dispname, strlen(dispname));
                    SetTextColor(hDC, RGB(255,255,255));
                    enab = TRUE;
                }
                else {
                    SetTextColor(hDC, RGB(128,128,128));
                }
                TextOut(hDC, txtbox[i].x, txtbox[i].y, txtbox[i].txt, strlen(txtbox[i].txt));
            }

            if (txtbox[i].idctrl >= 0)
                EnableWindow(btn[txtbox[i].idctrl].hBtn, enab);
        }

        SelectObject(hDC, hOldFont);
    }
}

/* ------------------------------------------------------------------------ 
 * Handles button presses. Remember that this occurs in the context of 
 * the Pump thread, not the simulator thread.
 * ------------------------------------------------------------------------ */

void flash_run (void)              
{
    EnableWindow(btn[IDC_RUN].hBtn, TRUE);      /* enable the run lamp */

    if (hFlashTimer != 0)
        KillTimer(hConsoleWnd, FLASH_TIMER_ID); /* (re)schedule lamp update */

    hFlashTimer = SetTimer(hConsoleWnd, FLASH_TIMER_ID, LAMPTIME, NULL);
}

void gui_run (int running)
{
    if (running && hUpdateTimer == 0 && hConsoleWnd != NULL) {
        hUpdateTimer = SetTimer(hConsoleWnd, UPDATE_TIMER_ID, 1000/UPDATE_INTERVAL, NULL);
    }
    else if (hUpdateTimer != 0 && ! running) {
        KillTimer(hConsoleWnd, UPDATE_TIMER_ID);
        hUpdateTimer = 0;
    }
    flash_run();                                /* keep run lamp active for a while after we stop running */
} 

void HandleCommand (HWND hWnd, WORD wNotify, WORD idCtl, HWND hwCtl)
{
    int i;

    switch (idCtl) {
        case IDC_POWER:                     /* toggle system power */
            power = ! power;
            if (running && ! power) {       /* turning off */
                reason = STOP_POWER_OFF;
                /* wait for execution thread to exit */
/* this prevents message pump from running, which unfortunately locks up
 * the emulator thread when it calls gui_run(FALSE) which calls EnableWindow on the Run lamp
 *              while (running)
 *                  Sleep(10);
 */             
            }
            stuff_and_wait("reset", 0, 500);

            btn[IDC_POWER_ON].state = power;
            EnableWindow(btn[IDC_POWER_ON].hBtn, power);

            for (i = 0; i < NBUTTONS; i++)  /* repaint all of the lamps */
                if (! btn[i].pushable)
                    InvalidateRect(btn[i].hBtn, NULL, TRUE);
            if ((cr_unit.flags & UNIT_ATT) && 
                (btn[IDC_1442].state!=STATE_1442_FULL)) {
                stuff_and_wait("detach cr", 0, 500);
                update_gui(TRUE);
            }
            program_is_loaded = FALSE;
            break;

        case IDC_PROGRAM_START:             /* begin execution */
            if (! running) {
                switch (RUNMODE) {
                    case MODE_INT_RUN:
                    case MODE_RUN:
                    case MODE_SI:
                        stuff_cmd("cont");
                        break;

                    case MODE_DISP:         /* display core and advance IAR */
                        ReadW(IAR);
                        IAR = IAR+1;
                        flash_run();        /* illuminate run lamp for .5 sec */
                        break;

                    case MODE_LOAD:         /* store to core and advance IAR */
                        WriteW(IAR, CES);
                        IAR = IAR+1;
                        flash_run();
                        break;
                }
            }
            break;

        case IDC_PROGRAM_STOP:
            if (running) {                  /* potential race condition here */
                GUI_BEGIN_CRITICAL_SECTION
                SETBIT(con_dsw, CPU_DSW_PROGRAM_STOP);
                SETBIT(ILSW[5], ILSW_5_INT_RUN_PROGRAM_STOP);
                int_req   |= INT_REQ_5;     /* note: calc_ints() is not needed in this case */
                int_lamps |= INT_REQ_5;
                GUI_END_CRITICAL_SECTION
            }
            break;

        case IDC_LOAD_IAR:
            if (! running) {
                IAR = CES & mem_mask;       /* set IAR from console entry switches */
            }
            break;

        case IDC_KEYBOARD:                  /* toggle between console/keyboard mode */
            break;

        case IDC_IMM_STOP:
            if (running) {
                reason = STOP_IMMEDIATE;    /* terminate execution without setting wait_mode */
                /* wait for execution thread to exit */
/* this prevents message pump from running, which unfortunately locks up
 * the emulator thread when it calls gui_run(FALSE) which calls EnableWindow on the Run lamp
 *              while (running)
 *                  Sleep(10);              
 */
            }
            break;

        case IDC_RESET:
            if (! running) {                /* check-reset is disabled while running */
                stuff_and_wait("reset", 0, 500);
                forms_check(0);             /* clear forms-check status */
                print_check(0);
            }
            if ((cr_unit.flags & UNIT_ATT) && 
                (btn[IDC_1442].state!=STATE_1442_FULL)) {
                stuff_and_wait("detach cr", 0, 500);
                update_gui(TRUE);
            }
            program_is_loaded = FALSE;
            break;

        case IDC_PROGRAM_LOAD:
            if (! running) {                /* if card reader is attached to a file, do cold start read of one card */
                IAR = 0;                    /* reset IAR */
#ifdef PROGRAM_LOAD_STARTS_CPU
                if (cr_unit.flags & UNIT_ATT)
                    stuff_cmd("boot cr");
                else {
                    if (((CES & 7) <= 4) &&
                        (dsk_unit[(CES & 7)].flags&UNIT_ATT))
                        boot_drive = CES & 7;
                    else
                        boot_drive = -1;
                    if (boot_drive >= 0) {
                        char cmd[50];

                        sprintf(cmd, "boot dsk%d", boot_drive);
                        stuff_cmd(cmd);
                    }
                }
#else
                if (cr_boot(0, NULL) != SCPE_OK)    /* load boot card */
                    remark_cmd("IPL failed");
#endif
            }
            break;

        case IDC_TEAR:                      /* "tear off printer output" */
        case IDC_1132:                      /* do same if they click on the printer icon */
            if (btn[IDC_1132].state && (wNotify == STN_CLICKED || wNotify == STN_DBLCLK))
                tear_printer();
            break;

        case IDC_1442:
            if (btn[IDC_1442].state == STATE_1442_FULL || wNotify == STN_DBLCLK) {
                if (running)
                    MessageBeep(0);
                else
                    stuff_cmd("detach cr");
            } else if (btn[IDC_1442].state != STATE_1442_EMPTY && wNotify == STN_CLICKED) {
                cr_rewind();
                update_gui(TRUE);
            }
            break;
    }
    
    SetForegroundWindow(hConsoleWindow);
    update_gui(FALSE);
}

/* ------------------------------------------------------------------------ 
 * ConsoleWndProc - window process for the console display
 * ------------------------------------------------------------------------ */

LRESULT CALLBACK ConsoleWndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    HDC hDC;
    PAINTSTRUCT ps;
    POINT p;
    RECT clip, xsect, rbmp;
    int i;

    switch (uMsg) {
        case WM_CLOSE:
            DestroyWindow(hWnd);
            break;

        case WM_DESTROY:
            gui_run(FALSE);
            hConsoleWnd = NULL;
            break;

        case WM_ERASEBKGND:
            hDC = (HDC) wParam;
            GetClipBox(hDC, &clip);
            SetRect(&rbmp, 0, 0, bmwid, bmht);
            if (IntersectRect(&xsect, &clip, &rbmp))
                BitBlt(hDC, xsect.left, xsect.top, xsect.right-xsect.left+1, xsect.bottom-xsect.top+1, hCDC, xsect.left, xsect.top, SRCCOPY);
            return TRUE;            /* let Paint do this so we know what the update region is (ps.rcPaint) */

        case WM_PAINT:
            hDC = BeginPaint(hWnd, &ps);
            DrawConsole(hDC, &ps);
            EndPaint(hWnd, &ps);
            break;

        case WM_COMMAND:                            /* button click */
            HandleCommand(hWnd, HIWORD(wParam), LOWORD(wParam), (HWND) lParam);
            break;

        case WM_CTLCOLOREDIT:                       /* text color for edit controls */
            SetBkColor((HDC) wParam, RGB(0,0,0));
            SetTextColor((HDC) wParam, RGB(255,255,255));
            break;

        case WM_DRAWITEM:
            PaintButton((LPDRAWITEMSTRUCT) lParam);
            break;

        case WM_SETCURSOR:
            GetCursorPos(&p);
            ScreenToClient(hWnd, &p);
            SetCursor(HandleClick(hWnd, p.x, p.y, FALSE, FALSE) ? hcHand : hcArrow);
            return TRUE;

        case WM_LBUTTONDOWN:
            HandleClick(hWnd, LOWORD(lParam), HIWORD(lParam), TRUE, FALSE);
            break;

        case WM_RBUTTONDOWN:
            HandleClick(hWnd, LOWORD(lParam), HIWORD(lParam), TRUE, TRUE);
            break;

        case WM_CTLCOLORBTN:
            i = GetWindowLongPtr((HWND) lParam, GWLP_ID);
            if (BETWEEN(i, 0, NBUTTONS-1))
                return (LRESULT) (power && IsWindowEnabled((HWND) lParam) ? btn[i].hbrLit : btn[i].hbrDark);

        case WM_TIMER:
            if (wParam == FLASH_TIMER_ID && hFlashTimer != 0) {
                KillTimer(hWnd, FLASH_TIMER_ID);
                hFlashTimer = 0;
            }
            update_gui(FALSE);
            break;

        case WM_DROPFILES:
            accept_dropped_file((HANDLE) wParam);       /* console window - dragged file is a script or card deck */
            break;

        default:
            return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }

    return 0;
}

enum {PRINTER_OK = 0, FORMS_CHECK = 1, PRINT_CHECK = 2, BOTH_CHECK = 3} printerstatus = PRINTER_OK;

void forms_check (int set)
{
    COLORREF oldcolor = btn[IDC_FORMS_CHECK].clr;

    if (set)
        SETBIT(printerstatus, FORMS_CHECK);
    else
        CLRBIT(printerstatus, FORMS_CHECK);

    btn[IDC_FORMS_CHECK].clr = (printerstatus & PRINT_CHECK) ? RGB(255,0,0) : RGB(255,255,0);

    btn[IDC_FORMS_CHECK].state = printerstatus;

    if (btn[IDC_FORMS_CHECK].hBtn != NULL) {
        EnableWindow(btn[IDC_FORMS_CHECK].hBtn, printerstatus);

        if (btn[IDC_FORMS_CHECK].clr != oldcolor)
            InvalidateRect(btn[IDC_FORMS_CHECK].hBtn, NULL, TRUE);      /* change color in any case */
    }
}

void print_check (int set)
{
    COLORREF oldcolor = btn[IDC_FORMS_CHECK].clr;

    if (set)
        SETBIT(printerstatus, PRINT_CHECK);
    else
        CLRBIT(printerstatus, PRINT_CHECK);

    btn[IDC_FORMS_CHECK].clr = (printerstatus & PRINT_CHECK) ? RGB(255,0,0) : RGB(255,255,0);

    btn[IDC_FORMS_CHECK].state = printerstatus;

    if (btn[IDC_FORMS_CHECK].hBtn != NULL) {
        EnableWindow(btn[IDC_FORMS_CHECK].hBtn, printerstatus);

        if (btn[IDC_FORMS_CHECK].clr != oldcolor)
            InvalidateRect(btn[IDC_FORMS_CHECK].hBtn, NULL, TRUE);      /* change color in any case */
    }
}

void keyboard_selected (int select)
{
    extern TMLN sim_con_ldsc;
    extern TMXR sim_con_tmxr;

    btn[IDC_KEYBOARD_SELECT].state = select;
    if (select &&                                           /* selected */
        (sim_con_tmxr.master != 0) &&                       /* not Telnet? */
        (sim_con_ldsc.serport != 0))                       /* and not serial? */
        SetForegroundWindow(hConsoleWindow);
    if (btn[IDC_KEYBOARD_SELECT].hBtn != NULL)
        EnableWindow(btn[IDC_KEYBOARD_SELECT].hBtn, select);
}

void disk_ready (int ready)
{
    btn[IDC_FILE_READY].state = ready;

    if (btn[IDC_FILE_READY].hBtn != NULL)
        EnableWindow(btn[IDC_FILE_READY].hBtn, ready);
}

void disk_unlocked (int unlocked)
{
    btn[IDC_DISK_UNLOCK].state = unlocked;

    if (btn[IDC_DISK_UNLOCK].hBtn != NULL)
        EnableWindow(btn[IDC_DISK_UNLOCK].hBtn, unlocked);
}

static BOOL is_scp_file(const char *filename)
{
    char cbuf[4*CBUFSIZE], gbuf[CBUFSIZE];
    char *argv[1] = {NULL};
    FILE *f = fopen(filename, "r");
    int lines = 0, comment_lines = 0;
    BOOL result = TRUE;
    size_t i;

    if (!f)
        return FALSE;
    while (result) {
        CONST char *cptr;

        cptr = fgets(cbuf, sizeof(cbuf), f);
        if (cptr == NULL)
            break;
        if (strlen(cptr) == sizeof(cbuf)-1)             /* VERY long lines are not SCP commands */
            result = FALSE;
        if (!strchr(cptr, '\n'))                        /* lines without newlines are not SCP commands */
            result = FALSE;
        if (!memcmp(cptr,"!// ", 4))                    /* indirect deck file literals are not SCP commands */
            result = FALSE;
        cptr = sim_trim_endspc(cbuf);
        while (sim_isspace (*cptr))                     /* trim leading space */
            cptr++;
        ++lines;
        for (i = 0; i < strlen(cptr); i++)
            if ((cptr[i] & 0x80) || 
                ((!isprint(cptr[i])) && (!isspace(cptr[i])))) 
                result = FALSE;                         /* SCP files only have printable ASCII */
        if ((*cptr == ';') || (*cptr == '#')) {         /* ignore comments */
            ++comment_lines;
            continue;
        }
        if (*cptr == 0)                                 /* ignore blank */
            continue;
        sim_sub_args (cbuf, sizeof(cbuf), argv);
        cptr = get_glyph_cmd (cptr, gbuf);              /* get command glyph */
        if (!find_cmd (gbuf)) {                         /* lookup command */
            result = FALSE;
            break;
        }
    }
    fclose(f);
    if (lines == 0)                                     /* Empty file isn't SCP */
        result = FALSE;
    return result;
}

static void accept_dropped_file (HANDLE hDrop)
{
    int nfiles;
    char fname[MAX_PATH], cmd[MAX_PATH+50], *deckfile;
    BOOL cardreader;
    BOOL scp_file;
    POINT pt;
    HWND hWndDrop;
    char msg[512];

    msg[sizeof(msg)-1] = '\0';
    nfiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);     /* get file count, */
    DragQueryFile(hDrop, 0, fname, sizeof(fname));          /* get first filename */
    DragQueryPoint(hDrop, &pt);                             /* get location of drop */
    DragFinish(hDrop);

    if (nfiles <= 0)                            /* hmm, this seems unlikely to occur, but better check */
        return;

    if (running) {                              /* can only accept a drop while processor is stopped */
        MessageBeep(0);
        return;
    }

    if ((hWndDrop = ChildWindowFromPoint(hConsoleWnd, pt)) == btn[IDC_1442].hBtn)
        cardreader = TRUE;                                  /* file was dropped onto 1442 card reader */
    else if (hWndDrop == NULL || hWndDrop == hConsoleWnd)
        cardreader = FALSE;                                 /* file was dropped onto console window, not a button */
    else {
        MessageBeep(0);                                     /* file was dropped onto another button */
        return;
    }

    if (nfiles > 1) {                           /* oops, we wouldn't know what order to read them in */
        MessageBox(hConsoleWnd, "You may only drop one file at a time", "", MB_OK);
        return;
    }

    scp_file = is_scp_file(fname);
    if (cardreader) {
        if (scp_file) {
            snprintf(msg, sizeof(msg)-1, "\"%s\"\r\n\r\nContains SCP commands (not card reader input).\r\n\r\nProcess as SCP commands?", fname); 
            if (IDYES != MessageBox(hConsoleWnd, msg, "", MB_YESNO))
                return;
            cardreader = FALSE;                 /* Process as SCP commands */
        }
    } else {
        if (!scp_file) {
            snprintf(msg, sizeof(msg)-1, "Invalid SCP command file:\r\n\r\n\"%s\"\r\n\r\nProcess as Card Reader Input?", fname);
            if (IDYES != MessageBox(hConsoleWnd, msg, "", MB_YESNO))
                return;
            cardreader = TRUE;                  /* Process as Card Input */
        }
    }
                                                /* if shift key is down, prepend @ to name (make it a deck file) */
    deckfile = ((GetKeyState(VK_SHIFT) & 0x8000) && cardreader) ? "@" : "";

    sprintf(cmd, "%s \"%s%s\"", cardreader ? "attach cr" : "do", deckfile, fname);
    stuff_cmd(cmd);
}

static void tear_printer (void)
{
    char cmd[MAX_PATH+100], filename[MAX_PATH];

    if ((prt_unit.flags & UNIT_ATT) == 0)
        return;

    if (running) {                                      /* can only accept a drop while processor is stopped */
        MessageBeep(0);
        return;
    }

    strcpy(filename, prt_unit.filename);                /* save current attached filename */

    if (! stuff_and_wait("detach prt", 1000, 0))        /* detach it */
        return;

    sprintf(cmd, "view \"%s\"", filename);              /* spawn notepad to view it */
    if (! stuff_and_wait(cmd, 3000, 2000))
        return;

    remove(filename);                                   /* delete the file */

    sprintf(cmd, "attach prt %s", filename);            /* reattach */
    stuff_cmd(cmd);
}

#ifdef XXX
    if ((hBtn = CreateWindow("BUTTON", btn[i].txt, WS_CHILD|WS_VISIBLE|BS_CENTER|BS_MULTILINE|BS_OWNERDRAW,
            x, y, BUTTON_WIDTH, BUTTON_HEIGHT, hwParent, (HMENU) i, hInstance, NULL)) == NULL)
        return NULL;

#endif

CRITICAL_SECTION critsect;

void begin_critical_section (void)
{
    static BOOL mustinit = TRUE;

    if (mustinit) {
        InitializeCriticalSection(&critsect);
        mustinit = FALSE;
    }

    EnterCriticalSection(&critsect);
}

void end_critical_section (void)
{
    LeaveCriticalSection(&critsect);
}

#ifndef MIN
#  define MIN(a,b) (((a) <= (b)) ? (a) : (b))
#endif

/* win32 - use a separate thread to read command lines so the GUI
 * can insert commands as well */

static HANDLE hCmdThread     = NULL;
static DWORD  iCmdThreadID   = 0;
static HANDLE hCmdReadEvent  = NULL;
static HANDLE hCmdReadyEvent = NULL;
static BOOL   scp_reading = FALSE;
static long   scp_command = 0;
static char   cmdbuffer[256];
static BOOL   read_exiting = FALSE;

#define SCP_COMMAND InterlockedExchangeAdd(&scp_command, 0L)
#define NEXT_SCP_COMMAND InterlockedIncrement(&scp_command)

/* CmdThread - separate thread to read commands from stdin upon request */

static DWORD WINAPI CmdThread (LPVOID arg)
{
    HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
    DWORD dwBytesRead;

    SetEvent(hCmdReadyEvent);                           /* notify main thread a line is ready */
    for (;;) {
        if (WAIT_TIMEOUT == WaitForSingleObject(hCmdReadEvent, 2000))   /* wait for request */
            continue;                                           /* put breakpoint here to debug */
        if (read_exiting)
            break;
        scp_reading = FALSE;
        if (ReadFile(hStdIn, cmdbuffer, sizeof(cmdbuffer)-1, &dwBytesRead, NULL)) {
            cmdbuffer[dwBytesRead] = '\0';
            scp_reading = FALSE;
            NEXT_SCP_COMMAND;
            SetEvent(hCmdReadyEvent);                           /* notify main thread a line is ready */
        } else {
            DWORD dwError = GetLastError();

            scp_reading = FALSE;
            NEXT_SCP_COMMAND;
        }
    }
    return 0;
}                                   

static void read_atexit (void)
{
    typedef BOOL (WINAPI *_func)(HANDLE, LPOVERLAPPED);
    _func pCancelIoEx;

    read_exiting = TRUE;
    pCancelIoEx = (_func)GetProcAddress(GetModuleHandleA("kernel32.dll"), "CancelIoEx");
    if (pCancelIoEx) {
        pCancelIoEx(GetStdHandle(STD_INPUT_HANDLE), NULL);
        SetEvent(hCmdReadEvent);                            /* wake read thread */
        WaitForSingleObject(hCmdThread, INFINITE);
    }
    CloseHandle(hCmdReadyEvent);
    hCmdReadyEvent = NULL;
    CloseHandle(hCmdReadEvent);
    hCmdReadEvent = NULL;
    CloseHandle(hCmdThread);
    hCmdThread = NULL;
}

char *read_cmdline (char *ptr, int size, FILE *stream)
{
    char *cptr;

    if (hCmdThread == NULL) {                               /* set up command-reading thread */
        if ((hCmdReadEvent  = CreateEvent(NULL, FALSE, FALSE, NULL)) == NULL)
            scp_panic("Can't create command line read event");

        if ((hCmdReadyEvent = CreateEvent(NULL, FALSE, FALSE, NULL)) == NULL)
            scp_panic("Can't create command line ready event");
                                                                /* start up the command thread */
        if ((hCmdThread = CreateThread(NULL, 0, CmdThread, NULL, 0, &iCmdThreadID)) == NULL)
            scp_panic("Unable to create command line reading thread");
        atexit(read_atexit);
        WaitForSingleObject(hCmdReadyEvent, INFINITE); /* wait for read thread to start */
    }

    update_gui(TRUE);
    SetEvent(hCmdReadEvent);                                /* let read thread get one line */
    WaitForSingleObject(hCmdReadyEvent, INFINITE);          /* wait for read thread or GUI to respond */
    strncpy(ptr, cmdbuffer, MIN(size, sizeof(cmdbuffer)));  /* copy line to caller's buffer */

    for (cptr = ptr; isspace(*cptr); cptr++)                /* absorb spaces */
        ;

    return cptr;
}

/* stuff_cmd - force a command into the read_cmdline output buffer. Called asynchronously by GUI */

long stuff_cmd (char *cmd)
{
    INPUT_RECORD *ip;
    size_t i, j, cmdsize = strlen(cmd);
    DWORD dwEventsWritten;
    long scp_cmd = SCP_COMMAND;

    ip = (INPUT_RECORD *)calloc(2+2*cmdsize, sizeof(*ip));
    for (i=j=0; i<cmdsize; i++, j++) {
        ip[j].EventType = KEY_EVENT;
        ip[j].Event.KeyEvent.bKeyDown = TRUE;
        ip[j].Event.KeyEvent.wRepeatCount = 1;
        ip[j].Event.KeyEvent.uChar.AsciiChar = cmd[i];
        j++;
        ip[j] = ip[j-1];
        ip[j].Event.KeyEvent.bKeyDown = FALSE;
    }
    ip[j].EventType = KEY_EVENT;
    ip[j].Event.KeyEvent.bKeyDown = TRUE;
    ip[j].Event.KeyEvent.wRepeatCount = 1;
    ip[j].Event.KeyEvent.uChar.AsciiChar = '\r';
    j++;
    ip[j] = ip[j-1];
    ip[j].Event.KeyEvent.bKeyDown = FALSE;
    WriteConsoleInput(GetStdHandle(STD_INPUT_HANDLE), ip, 2+j, &dwEventsWritten);
    free(ip);
    return scp_cmd;
}

/* my_yield - process GUI messages. It's not apparent why stuff_and_wait would block,
 * since it sleeps in the GUI thread while scp runs in the main thread. However,
 * at the end of every command scp calls update_gui, which can result in messages
 * being sent to the GUI thread. So, the GUI thread has to process messages while
 * stuff_and_wait is waiting.
 */
static void my_yield (void)
{
    MSG msg;
                    /* multitask */
    while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

/* stuff_and_wait -- stuff a command and wait for the emulator to process the command
 * and come back to prompt for another
 */

t_bool stuff_and_wait (char *cmd, int timeout, int delay)
{
    long scp_cmd = stuff_cmd(cmd);

    while (scp_cmd == SCP_COMMAND) {
        if (timeout < 0)
            return FALSE;

        my_yield();
        if (scp_cmd != SCP_COMMAND)
            break;

        Sleep(50);
        if (timeout)
            if ((timeout -= 50) <= 0)
                timeout = -1;

        my_yield();
    }

    if (delay)
        Sleep(delay);

    return TRUE;
}

/* remark_cmd - print a remark from inside a command processor. This routine takes
 * into account the possiblity that the command might have been stuffed, in which
 * case the sim> prompt needs to be reprinted.
 */

void remark_cmd (char *remark)
{
    if (scp_reading)
        sim_printf("\n");

    sim_printf("%s\n", remark);

    if (scp_reading)
        sim_printf("%s", sim_prompt);
}

#endif      /* _WIN32 defined */
#endif      /* GUI_SUPPORT defined */
